from Sensor import Sensor
from BonyLandmark import BonyLandmark
from Rule import Rule
from ConfigParser import ConfigParser
import wx
import sys, os

##  Parses the configuration file.
class ConfigfileReader:

    ##  The constructor of ConfigfileReader. Opens the config file config.ini and reads the entire content.
    #   @param[in]  gui             GUI instance.
    #   @param[in]  configString    The path to the config.ini file.
    def __init__(self, gui, configString):
        ##  Instantiation of Python's ConfigParser.
        self.config = ConfigParser()
        ##  The path to the config.ini file.
        self.configFile = configString
        ##  GUI instance.
        self.gui = gui
        ##  The root directory of the application.
        self.root = self.ReadRootDirectory()
        
        try:
            self.config.read(self.configFile)
        except:
            msg = "Could not read the configuration file %s." % self.configFile
            self.DisplayErrorDialog(msg)

    ##  Reads the GH-Joints data.
    #   @param[in]  allGHJoints     a Boolean value, true to return all GHJoints, whether they are active or not
    #   @return[dict]   A dictionary containing the number and names of GH-Joints.
    def ReadGHJoints(self, allGHJoints):
        try:
            ghJoints = self.config.items("GH-Joints")
        except:
            msg = "An error ocurred while reading GH-Joints data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        # Make list of active bony landmarks.
        activeBLCheck = []
        for blList in self.ReadActiveBonyLandmarks(): activeBLCheck += blList[1 :]
        gh_dict = {}

        for ghJointNumber, ghj in ghJoints:
            # If all the GHJoints are included or
            # If the gh-joint is an active bl, then it is added to the list.
            if allGHJoints or int(ghJointNumber) in activeBLCheck:
                gh_dict[int(ghJointNumber)] = ghj
        return gh_dict

    ##  Calculates the connection between the GH-Joints and bony landmarks.
    #   @return[dict]   A dictionary containing each GH-Joint and their corresponding bony landmark connections.
    def ReadGHJointsRegressionConnection(self):
        try:
            regCon = self.config.items("GH-Joints Regression Connection")
        except:
            msg = "An error ocurred while reading GH-Joints Regression Connection data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        blActiveList = self.ReadActiveBonyLandmarks()
        activeCheck = []
        # Make list of active bony landmarks.
        for blList in blActiveList: activeCheck += blList[1 :]
        
        reCon_dict = {}
        for key, connections in regCon:
            conActive = True
            conStrings = connections.split(" ")

            # Convert the indices to integers.
            for i in range(0,len(conStrings)):
                conStrings[i] = int(conStrings[i])
                # Check if the bony landmark is active.
                conActive = conActive and conStrings[i] in activeCheck
            
            # Make a new entry in the dictionary if both the GH-Joint and it's bl connections are active.
            if conActive and int(key)in activeCheck:
                reCon_dict[int(key)] = conStrings
        return reCon_dict

    ##  The GSI mapping from fob0_x to COM[port]_[address] as read from the config.ini file.
    #   @return[dict]   A dictionary containing a mapping between GSI ID's and COM ports.
    def ReadComports(self):
        try:
            comports = self.config.items("FobDevice")
        except:
            msg = "An error ocurred while reading FobDevice data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        com_dict = {}
        for gsi, com in comports:
            com_dict[gsi.split('_')[1]] = com
        return com_dict

    ##  Reads the positions of the sensors to be displayed on the body in SensorPanel.
    #   @return[dict]   A dictionary containing the sensor and corresponding coordinates on the body.
    def ReadSensorPictPos(self):
        try:
            sensorcoord = self.config.items("SensorPictPos")
        except:
            msg = "An error ocurred while reading SensorPictPos data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        spp_dict = {}
        for sensor, coord in sensorcoord:
            if coord[0] + coord[-1] != "()":
                raise ValueError("Badly formatted string (missing brackets).")
            items = coord[1:-1]
            pos = [int(i.strip()) for i in items.split(',')]
            spp_dict[sensor] = tuple(pos)
        return spp_dict

    ##  Creates a list of sensors, read from the [ActiveSensors] section in the config.ini file.
    #   @return[array]  The list of active sensors.
    def ReadActiveSensors(self):
        try:
            ass = self.config.get("ActiveSensors", "active_sensors")
        except:
            msg = "An error ocurred while reading ActiveSensors data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        asl = [i.strip() for i in ass.split(',')]
        return asl

    ##  Read the default positions of the sensors. This only returns positions that are associated with active sensors!
    #   @return[dictionary] A (key,value) mapping from sensor name to position, e.g. {'fob0_3', 'Thorax'}.
    def ReadDefaultPositions(self):
        try:
            positions = self.config.items("DefaultPositions")
        except:
            msg = "An error ocurred while reading DefaultPositions data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        asl = self.ReadActiveSensors()
        dp_dict = {}
        for gsi, position in positions:
            if gsi in asl:
                dp_dict[gsi] = position
        return dp_dict

    ##  Creates an array containing all the bony landmarks of the default protocol.
    #   @return[array]  The list of all the bony landmarks of the default protocol.
    def ReadBonyLandmarks(self):
        try:
            names = self.config.items("BonyLandmarks")
        except:
            msg = "An error ocurred while reading BonyLandmarks data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        for i in range(0,len(names)):
            names[i] = (int(names[i][0]), names[i][1])
        names.sort()

        bonyLandmarks = []
        activeSensors = self.ReadActiveSensors()
        for name in names:
            bonyLandmarks.append(BonyLandmark(name[0], name[1]))

        # Add GH-joint BLs.
        for ghBlNumber, ghBlName in self.ReadGHJoints(True).items():
            bonyLandmarks.append(BonyLandmark(ghBlNumber, ghBlName))

        # Sets the sensorID For each bony landmark connected to an active sensor.
        for blList in self.ReadActiveBonyLandmarks():
            # Set the sensorID of each BL (defaults to -1).
            for i in range(1,len(blList)): bonyLandmarks[blList[i]].sensorID = blList[0]

        # Remove the bony landmarks that don't have any active sensor connections.
        updatedBonyLandmarks = list(bonyLandmarks)
        for bonyLandmark in bonyLandmarks:
            if bonyLandmark.sensorID == -1:
                updatedBonyLandmarks.remove(bonyLandmark)

        return updatedBonyLandmarks
    
    ##  Creates an array containing all the names of the bony landmarks of the default protocol.
    #   @return[array]  The list of all the names of the bony landmarks of the default protocol.
    def ReadBonyLandmarkNames(self):
        try:
            names = self.config.items("BonyLandmarks")
        except:
            msg = "An error ocurred while reading BonyLandmarks data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        for i in range(0,len(names)):
            names[i] = (int(names[i][0]), names[i][1])
        names.sort()
        
        return names;

    ## Returns a list of bony landmarks indices arrays with the sensorID of the active sensor they are coupled to.
    #   @return[array]  Lists of bony landmark indices and the coupled active sensors ID's.
    def ReadActiveBonyLandmarks(self):
        # Read BL relative sensors.
        try:
            sensor_bl_connection_list = self.config.items("Sensor-BL-Connection")
        except:
            msg = "An error ocurred while reading Sensor-BL-Connection data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        try:
            active_sensors = self.config.items("ActiveSensors")
        except:
            msg = "An error ocurred while reading ActiveSensors data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        sensor_bl_connection_list.sort()
        bls = []
        for sensor_bl_connection in sensor_bl_connection_list:
            # Set the Sensors of the bony landmarks using the active sensor ID's.
            if sensor_bl_connection[0] in active_sensors[0][1]:
                sensorID = sensor_bl_connection[0]
                bonyLandmarkList = [int(blID) for blID in (sensor_bl_connection[1].split())]
                bonyLandmarkList.insert(0,sensorID)
                bls.append(bonyLandmarkList) #list of BL ids
        # The first entry will always be emptpy or contain fob0_2.
        return bls[1 :]


    ##  Read all the bony landmark descriptions.
    #   @return[array]  An array of strings containing the bony landmark descriptions.
    def ReadDescriptions(self):
        try:
            descriptions = self.config.items("BL Descriptions")
        except:
            msg = "An error ocurred while reading BL Descriptions data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        for i in range(0,len(descriptions)):
            descriptions[i] = (int(descriptions[i][0]), descriptions[i][1])
        descriptions.sort()

        for i in range(0,len(descriptions)):
            descriptions[i] = descriptions[i][1]

        return descriptions

    ##  Read the rulebase for bony landmarks.
    #   @return[array]  All the rules for bony landmarks.
    def ReadBLRulebase(self):
        return self.__ReadRulebase("BLRulebase", "BonyLandmark")

    ##  Read the rulebase for sensors.
    #   @return[array]  All the rules for sensors.
    def ReadSensorRulebase(self):
        return self.__ReadRulebase("SensorRulebase", "Sensor")

    ##  Returns a rulebase (a list of Rule objects) from the given string.
    #   @param[in]  rulebase    The rulebase to be loaded, BLRulebase or SensorRulebase.
    #   @param[in]  ruleType    The type of rule, Sensor or BonyLandmark.
    #   @return[array]          The rulebase corresponding to ruleType.
    def __ReadRulebase(self, rulebase, ruleType):
        try:
            rules = self.config.items(rulebase)
        except:
            msg = "An error ocurred while reading %s data. Please check the configuration file %s for inconsistencies." % rulebase, self.configFile
            self.DisplayErrorDialog(msg)

        rules.sort()

        rulebase = []
        activeCheck = []

        if ruleType == "BonyLandmark":
            blActiveList = self.ReadActiveBonyLandmarks()
            # Make list of active bony landmarks.
            for blList in blActiveList: activeCheck += blList[1 :]
            # Convert all the items from integers to strings.
            for i in range(0,len(activeCheck)): activeCheck[i] = '%s' % activeCheck[i]
        else:
            activeCheck = self.config.items("ActiveSensors")[0][1]

        for rule in rules:
            components = rule[1].split()
            if components[1] in activeCheck and components[3] in activeCheck:
                if ruleType == "BonyLandmark": rulebase.append(Rule(components[0], int(components[1]), int(components[3]), ruleType))
                else: rulebase.append(Rule(components[0], components[1], components[3], ruleType))

        return rulebase
    
    ##  Returns a list containing the 3D model numbers and their connected bony landmarks.
    #   @return[array]  Array containing 3d model numbers and their bony landmarks.
    def Read3DModelConnection(self):
        try:
            modelConnection = self.config.items("3DModelConnection")
        except:
            msg = "An error ocurred while reading 3DModelConnection data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        validConnections = []
        activeBonyLandmarks = []

        # Make a list containing the active bony landmarks.
        for blList in self.ReadActiveBonyLandmarks():
            activeBonyLandmarks += blList[1 :]

        # Process each line from the config file
        for i in range(0, len(modelConnection)):
            validConnection = True
            bonyLandmarkList = []
            modelList = []

            # Make a list of the 3d model numbers.
            modelnumbers = modelConnection[i][0].split()
            if len(modelnumbers) == 3 and modelnumbers[1] == '-':
                modelList = range(int(modelnumbers[0]),int(modelnumbers[2])+1)
            else:
                for j in range(0,len(modelnumbers)):
                    modelList.append(int(modelnumbers[j]))

            # Make a list of the bony landmarks numbers. Sets the validConnection bool to false if
            # one of the bony landmarks in the 3D Model - Bonylandmark connection is not in the active bonylandmarks.
            for bC in modelConnection[i][1].split():
                validConnection =  int(bC) in activeBonyLandmarks and validConnection
                bonyLandmarkList.append(int(bC))

            # If it is a valid connection, add the model numbers and bony landmark numbers to the list.
            if validConnection:
                validConnections.append([modelList, bonyLandmarkList])

        return validConnections

    ## Returns the number of parts of the 3d Model.
    #  @return[int] The number of elements in the 3D Model.
    def Read3DModelParts(self):
        try:
            parts = int(self.config.items("3DModelParts")[0][1])
        except:
            msg = "An error ocurred while reading 3DModelParts data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        return parts

    ## Returns a string containing the path and the format of the 3D model VTK files.
    #  @return[string]  Path and format of the 3D model VTK files.
    def Read3DFileName(self):
        try:
            filename =  os.path.join(self.root, self.config.items("3DFileName")[0][1])
        except:
            msg = "An error ocurred while reading 3DFileName data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)

        return filename

    ##  Determines whether 3D smoothing is on or off.
    #   @return[Boolean]    Boolean representing the 3D smoothing of the model.
    def Read3DSmoothing(self):
        try:
            smoothing = self.config.items("3DSmoothing")[0][1] == "True"
        except:
            msg = "An error ocurred while reading 3DSmooth data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)
        return smoothing
        
    ##  Reads the framerate of the application.
    #   @return[float]  The framerate.
    def ReadFramerate(self):
        try:
            framerate = self.config.items("Framerate")[0][1]
        except:
            msg = "An error ocurred while reading Framerate data. Please check the configuration file %s for inconsistencies." % self.configFile
            self.DisplayErrorDialog(msg)
        return float(framerate)

    ## Returns a string containing the root directory path of the FoB installation.
    #  @return[string]  The root directory path.
    def ReadRootDirectory(self):
        if hasattr(sys, 'frozen') and sys.frozen:
            root, exe = os.path.split(sys.executable)
        else:
            dirname = os.path.dirname(sys.argv[0])
            if dirname and dirname != os.curdir:
                root = dirname
            else:
                root = os.getcwd()
        return root

    ## Displays an error dialog.
    # @param[in]    msg The message to be displayed in the dialog.
    def DisplayErrorDialog(self, msg):
        dlg = wx.MessageDialog(self.gui, msg, 'Warning', wx.OK | wx.NO_DEFAULT | wx.ICON_EXCLAMATION)
        dlg.ShowModal()
